import pandas as pd
from nicegui import ui
import plotly.express as px
import numpy as np
import os
from typing import Dict, Any
from plotly.graph_objects import Figure
import scipy.stats as stats
from sklearn.linear_model import LinearRegression
from sklearn.decomposition import PCA


# 1. 数据加载与异常处理
def load_csv_data(file_path: str) -> pd.DataFrame | None:
    try:
        print(f"尝试加载数据文件: {file_path}")
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")
        df = pd.read_csv(file_path)
        print("数据加载成功，列名:", df.columns.tolist())
        ui.notify("数据加载成功！", type='positive')
        return df
    except Exception as e:
        error_msg = f"数据加载失败: {str(e)}"
        print(error_msg)
        ui.notify(error_msg, type='negative')
        return None


# 2. 数据初步检查
def check_data(df: pd.DataFrame) -> bool:
    if df is not None:
        print("\n===== 数据基本信息 =====")
        df.info()
        print(f"数据规模：{df.shape[0]}行 × {df.shape[1]}列")
        print("数据前5行预览：")
        print(df.head())
        return True
    print("无数据可检查")
    return False


# 3. 数据预处理函数
def preprocess_data(df: pd.DataFrame) -> pd.DataFrame:
    print("\n开始数据预处理...")
    processed_df = df.copy()
    numeric_cols = ['age', 'study_hours_per_day', 'social_media_hours', 'netflix_hours',
                    'attendance_percentage', 'sleep_hours', 'exam_score']
    valid_numeric_cols = [col for col in numeric_cols if col in processed_df.columns]
    for col in valid_numeric_cols:
        processed_df[col] = processed_df[col].fillna(processed_df[col].mean())
        print(f"填充数值列缺失值: {col}")

    cat_cols = ['gender', 'part_time_job', 'diet_quality', 'parental_education_level',
                'internet_quality', 'extracurricular_participation']
    valid_cat_cols = [col for col in cat_cols if col in processed_df.columns]
    for col in valid_cat_cols:
        processed_df[col] = processed_df[col].fillna(processed_df[col].mode()[0])
        print(f"填充分类列缺失值: {col}")

    if 'age' in processed_df.columns:
        processed_df = processed_df.query("16 <= age <= 25")
        print("过滤年龄在16-25岁之间的数据")
    if 'social_media_hours' in processed_df.columns:
        processed_df = processed_df.query("0 <= social_media_hours <= 15")
        print("过滤社交媒体使用时长在0-15小时之间的数据")

    categorical_cols = ['gender', 'part_time_job', 'diet_quality', 'internet_quality',
                        'extracurricular_participation']
    valid_categorical_cols = [col for col in categorical_cols if col in processed_df.columns]
    processed_df = pd.get_dummies(processed_df, columns=valid_categorical_cols, prefix_sep='_')
    print(f"对分类列进行独热编码: {valid_categorical_cols}")
    print("数据预处理完成")
    return processed_df


# 4. 数据降维函数（PCA）
def reduce_data_dimension(df: pd.DataFrame, n_components: int = 2) -> tuple[pd.DataFrame, list]:
    print("\n开始数据降维...")
    numeric_cols = df.select_dtypes(include=['float64', 'int64']).columns.tolist()
    numeric_df = df[numeric_cols].copy()
    pca = PCA(n_components=n_components)
    reduced_data = pca.fit_transform(numeric_df)
    reduced_df = pd.DataFrame(
        data=reduced_data,
        columns=[f'主成分{i + 1}' for i in range(n_components)]
    )
    explained_variance = pca.explained_variance_ratio_
    print(f"PCA降维结果：{n_components}个主成分解释方差比例：{[round(v, 4) for v in explained_variance]}")
    return reduced_df, explained_variance


# 5. 统计分析
def data_analysis(df: pd.DataFrame, reduced_df: pd.DataFrame = None) -> Dict[str, Any]:
    print("\n开始统计分析...")
    results = {}
    # 相关性分析
    if 'social_media_hours' in df.columns and 'exam_score' in df.columns:
        results['media_correlation'] = df[['social_media_hours', 'exam_score']].corr()
    if 'study_hours_per_day' in df.columns and 'exam_score' in df.columns:
        results['study_correlation'] = df[['study_hours_per_day', 'exam_score']].corr()
    required_cols = ['social_media_hours', 'study_hours_per_day', 'sleep_hours', 'exam_score']
    if all(col in df.columns for col in required_cols):
        results['correlation'] = df[required_cols].corr()

    # 分组分析
    group_analyses = {}
    if 'gender_Female' in df.columns and 'exam_score' in df.columns:
        gender_groups = df.groupby('gender_Female')['exam_score'].agg(['mean', 'std', 'count'])
        gender_groups.index = ['男性', '女性']
        group_analyses['gender'] = gender_groups
    if 'part_time_job_Yes' in df.columns and 'exam_score' in df.columns:
        job_groups = df.groupby('part_time_job_Yes')['exam_score'].agg(['mean', 'std', 'count'])
        job_groups.index = ['无兼职', '有兼职']
        group_analyses['part_time_job'] = job_groups
    results['group_analyses'] = group_analyses

    # 回归分析
    if 'study_hours_per_day' in df.columns and 'exam_score' in df.columns:
        X = df[['study_hours_per_day']].values
        y = df['exam_score'].values
        reg = LinearRegression().fit(X, y)
        results['regression'] = {
            'coefficient': reg.coef_[0],
            'intercept': reg.intercept_,
            'r2': reg.score(X, y)
        }

    # 显著性检验
    if 'social_media_hours' in df.columns and 'exam_score' in df.columns:
        median = df['social_media_hours'].median()
        high_media = df[df['social_media_hours'] >= median]['exam_score']
        low_media = df[df['social_media_hours'] < median]['exam_score']
        t_stat, p_value = stats.ttest_ind(high_media, low_media, equal_var=False)
        results['t_test'] = {'t_statistic': t_stat, 'p_value': p_value, 'significant': p_value < 0.05}

    if reduced_df is not None:
        results['reduced_data'] = reduced_df
    return results


# 6. 数据可视化
def create_visualizations(df: pd.DataFrame, analysis_results: Dict[str, Any], explained_variance: list = None) -> Dict[str, Figure]:
    print("\n开始创建可视化图表...")
    visualizations = {}
    # 散点图
    if 'social_media_hours' in df.columns and 'exam_score' in df.columns:
        try:
            fig_scatter = px.scatter(
                df, x='social_media_hours', y='exam_score',
                color='gender_Female' if 'gender_Female' in df.columns else None,
                trendline='ols', title='社交媒体使用时长与考试成绩的关系'
            )
        except:
            fig_scatter = px.scatter(
                df, x='social_media_hours', y='exam_score',
                color='gender_Female' if 'gender_Female' in df.columns else None,
                title='社交媒体使用时长与考试成绩的关系'
            )
        visualizations['scatter'] = fig_scatter

    # 直方图
    if 'exam_score' in df.columns:
        visualizations['histogram'] = px.histogram(df, x='exam_score', title='考试成绩分布')

    # 箱线图
    if 'study_hours_per_day' in df.columns and 'exam_score' in df.columns:
        visualizations['boxplot'] = px.box(
            df, x=pd.cut(df['study_hours_per_day'], bins=3, labels=['低', '中', '高']),
            y='exam_score', title='不同学习时间的考试成绩分布'
        )

    # 热力图
    if 'correlation' in analysis_results:
        visualizations['heatmap'] = px.imshow(
            analysis_results['correlation'], text_auto=True,
            title='生活习惯与成绩的相关性热力图'
        )

    # 性别对比图
    if 'group_analyses' in analysis_results and 'gender' in analysis_results['group_analyses']:
        gender_data = analysis_results['group_analyses']['gender'].reset_index()
        visualizations['gender_bar'] = px.bar(
            gender_data, x='index', y='mean', error_y='std',
            title='不同性别学生的平均成绩对比',
            labels={'index': '性别', 'mean': '平均考试成绩', 'std': '标准差'}
        )

    # 兼职对比图
    if 'group_analyses' in analysis_results and 'part_time_job' in analysis_results['group_analyses']:
        job_data = analysis_results['group_analyses']['part_time_job'].reset_index()
        visualizations['job_bar'] = px.bar(
            job_data, x='index', y='mean', error_y='std',
            title='兼职状态与平均成绩对比',
            labels={'index': '兼职状态', 'mean': '平均考试成绩', 'std': '标准差'}
        )

    # 回归图
    if 'regression' in analysis_results and 'study_hours_per_day' in df.columns:
        reg = analysis_results['regression']
        fig_reg = px.scatter(
            df, x='study_hours_per_day', y='exam_score',
            title=f'学习时间与成绩的回归分析 (R²={reg["r2"]:.2f})'
        )
        x_range = np.linspace(df['study_hours_per_day'].min(), df['study_hours_per_day'].max(), 100)
        fig_reg.add_scatter(
            x=x_range, y=reg['intercept'] + reg['coefficient'] * x_range,
            mode='lines', name=f'回归线: y={reg["coefficient"]:.2f}x + {reg["intercept"]:.2f}'
        )
        visualizations['regression'] = fig_reg

    # PCA散点图
    if 'reduced_data' in analysis_results and explained_variance is not None:
        reduced_df = analysis_results['reduced_data']
        if 'gender_Female' in df.columns:
            reduced_df['性别'] = df['gender_Female'].map({0: '男性', 1: '女性'})
        if 'exam_score' in df.columns:
            reduced_df['考试成绩'] = df['exam_score']
        visualizations['pca_scatter'] = px.scatter(
            reduced_df, x='主成分1', y='主成分2',
            color='性别' if '性别' in reduced_df.columns else None,
            size='考试成绩' if '考试成绩' in reduced_df.columns else None,
            size_max=30,
            title=f'PCA降维结果可视化（累计解释方差：{sum(explained_variance):.2%}）',
            labels={
                '主成分1': f'主成分1（解释方差：{explained_variance[0]:.2%}）',
                '主成分2': f'主成分2（解释方差：{explained_variance[1]:.2%}）'
            }
        )
    return visualizations


# 7. 构建界面（完整补充后）
def build_interface(initial_fig: Figure, df_cleaned: pd.DataFrame, reduced_df: pd.DataFrame) -> None:
    print("\n开始构建Web界面...")

    # 全局样式
    ui.add_css("""
        body { font-size: 16px; min-width: 1200px; }
        .nicegui-container { padding: 20px; }
    """)

    # 标题栏
    with ui.header().classes('bg-blue-100 p-6'):
        ui.label('学生社交媒体使用分析系统').classes('text-3xl font-bold')

    # 主内容区（左右分栏）
    with ui.row().classes('w-full h-[calc(100vh-80px)]'):
        # 左侧筛选栏（1/5宽度）
        with ui.column().classes('w-1/5 p-6 border-r border-gray-200'):
            ui.label('数据筛选').classes('text-2xl font-semibold mb-6')

            # 学习时间筛选
            study_min = study_max = None
            if 'study_hours_per_day' in df_cleaned.columns:
                ui.label('学习时间下限 (小时/天)').classes('text-lg mb-2')
                study_min = ui.slider(
                    min=df_cleaned['study_hours_per_day'].min(),
                    max=df_cleaned['study_hours_per_day'].max(),
                    value=df_cleaned['study_hours_per_day'].min()
                ).classes('w-full h-6')

                ui.label('学习时间上限 (小时/天)').classes('text-lg mb-2 mt-6')
                study_max = ui.slider(
                    min=df_cleaned['study_hours_per_day'].min(),
                    max=df_cleaned['study_hours_per_day'].max(),
                    value=df_cleaned['study_hours_per_day'].max()
                ).classes('w-full h-6')
            else:
                ui.label("无学习时间数据可供筛选").classes('text-lg text-gray-500')

            # 社交媒体时间筛选
            social_min = social_max = None
            if 'social_media_hours' in df_cleaned.columns:
                ui.label('社交媒体使用下限 (小时/天)').classes('text-lg mb-2 mt-8')
                social_min = ui.slider(
                    min=df_cleaned['social_media_hours'].min(),
                    max=df_cleaned['social_media_hours'].max(),
                    value=df_cleaned['social_media_hours'].min()
                ).classes('w-full h-6')

                ui.label('社交媒体使用上限 (小时/天)').classes('text-lg mb-2 mt-6')
                social_max = ui.slider(
                    min=df_cleaned['social_media_hours'].min(),
                    max=df_cleaned['social_media_hours'].max(),
                    value=df_cleaned['social_media_hours'].max()
                ).classes('w-full h-6')
            else:
                ui.label("无社交媒体使用时间数据可供筛选").classes('text-lg text-gray-500 mt-8')

            # 图表类型选择
            chart_types = {
                'scatter': '社交媒体使用-成绩散点图',
                'histogram': '成绩分布直方图',
                'boxplot': '学习时间-成绩箱线图',
                'heatmap': '相关性热力图',
                'gender_bar': '性别-成绩对比图',
                'job_bar': '兼职状态-成绩对比图',
                'regression': '学习时间-成绩回归图',
                'pca_scatter': 'PCA降维结果可视化'
            }
            chart_select = ui.select(
                options=list(chart_types.keys()),
                label='选择图表类型',
                value='scatter'
            ).classes('w-full mt-8 text-lg')

            # 刷新按钮
            refresh_btn = ui.button('刷新图表', on_click=lambda: update_chart()).classes(
                'mt-10 bg-blue-600 text-white text-lg py-3 px-6 rounded-lg hover:bg-blue-700 transition'
            )

        # 右侧图表展示区域（4/5宽度，与左侧平级）
        with ui.column().classes('w-4/5 p-6 overflow-hidden'):
            chart_title = ui.label('社交媒体使用时长与考试成绩的关系').classes('text-2xl font-semibold mb-6')
            plotly_container = ui.plotly(figure=initial_fig).classes('w-full h-[85%]')

        # 刷新图表逻辑（定义在主内容区，确保能访问所有控件）
        def update_chart():
            print(f"更新图表: {chart_select.value}")
            filtered_df = df_cleaned.copy()

            # 应用学习时间筛选
            if study_min and study_max:
                filtered_df = filtered_df[
                    (filtered_df['study_hours_per_day'] >= study_min.value) &
                    (filtered_df['study_hours_per_day'] <= study_max.value)
                ]

            # 应用社交媒体时间筛选
            if social_min and social_max:
                filtered_df = filtered_df[
                    (filtered_df['social_media_hours'] >= social_min.value) &
                    (filtered_df['social_media_hours'] <= social_max.value)
                ]

            # 检查筛选后数据是否为空
            if filtered_df.empty:
                ui.notify("筛选后数据为空，请调整筛选条件！", type='warning')
                return

            # 对筛选后的数据重新执行降维和分析
            reduced_filtered_df, explained_variance = reduce_data_dimension(filtered_df)
            analysis_res = data_analysis(filtered_df, reduced_filtered_df)

            new_fig = px.scatter(title='无可用数据')
            title = '无可用图表'
            chart_type = chart_select.value

            # 处理各类图表类型
            if chart_type == 'scatter' and 'social_media_hours' in filtered_df.columns and 'exam_score' in filtered_df.columns:
                new_fig = px.scatter(
                    filtered_df, x='social_media_hours', y='exam_score',
                    color='gender_Female' if 'gender_Female' in filtered_df.columns else None,
                    title='社交媒体使用时长与考试成绩的关系'
                )
                title = '社交媒体使用时长与考试成绩的关系'

            elif chart_type == 'histogram' and 'exam_score' in filtered_df.columns:
                new_fig = px.histogram(filtered_df, x='exam_score', title='考试成绩分布')
                title = '考试成绩分布'

            elif chart_type == 'boxplot' and 'study_hours_per_day' in filtered_df.columns and 'exam_score' in filtered_df.columns:
                new_fig = px.box(
                    filtered_df,
                    x=pd.cut(filtered_df['study_hours_per_day'], bins=3, labels=['低', '中', '高']),
                    y='exam_score', title='不同学习时间的考试成绩分布'
                )
                title = '不同学习时间的考试成绩分布'

            elif chart_type == 'heatmap' and 'correlation' in analysis_res:
                new_fig = px.imshow(analysis_res['correlation'], text_auto=True,
                                    title='生活习惯与成绩的相关性热力图')
                title = '生活习惯与成绩的相关性热力图'

            elif chart_type == 'gender_bar' and 'group_analyses' in analysis_res and 'gender' in analysis_res['group_analyses']:
                gender_data = analysis_res['group_analyses']['gender'].reset_index()
                new_fig = px.bar(
                    gender_data, x='index', y='mean', error_y='std',
                    title='不同性别学生的平均成绩对比',
                    labels={'index': '性别', 'mean': '平均考试成绩', 'std': '标准差'}
                )
                title = '不同性别学生的平均成绩对比'

            elif chart_type == 'job_bar' and 'group_analyses' in analysis_res and 'part_time_job' in analysis_res['group_analyses']:
                job_data = analysis_res['group_analyses']['part_time_job'].reset_index()
                new_fig = px.bar(
                    job_data, x='index', y='mean', error_y='std',
                    title='兼职状态与平均成绩对比',
                    labels={'index': '兼职状态', 'mean': '平均考试成绩', 'std': '标准差'}
                )
                title = '兼职状态与平均成绩对比'

            elif chart_type == 'regression' and 'regression' in analysis_res and 'study_hours_per_day' in filtered_df.columns:
                reg = analysis_res['regression']
                new_fig = px.scatter(
                    filtered_df, x='study_hours_per_day', y='exam_score',
                    title=f'学习时间与成绩的回归分析 (R²={reg["r2"]:.2f})'
                )
                x_range = np.linspace(
                    filtered_df['study_hours_per_day'].min(),
                    filtered_df['study_hours_per_day'].max(), 100
                )
                new_fig.add_scatter(
                    x=x_range, y=reg['intercept'] + reg['coefficient'] * x_range,
                    mode='lines', name=f'回归线: y={reg["coefficient"]:.2f}x + {reg["intercept"]:.2f}'
                )
                title = f'学习时间与成绩的回归分析 (R²={reg["r2"]:.2f})'

            elif chart_type == 'pca_scatter' and 'reduced_data' in analysis_res:
                pca_df = analysis_res['reduced_data'].copy()
                if 'gender_Female' in filtered_df.columns:
                    pca_df['性别'] = filtered_df['gender_Female'].map({0: '男性', 1: '女性'})
                if 'exam_score' in filtered_df.columns:
                    pca_df['考试成绩'] = filtered_df['exam_score']

                new_fig = px.scatter(
                    pca_df,
                    x='主成分1',
                    y='主成分2',
                    color='性别' if '性别' in pca_df.columns else None,
                    size='考试成绩' if '考试成绩' in pca_df.columns else None,
                    size_max=30,
                    title=f'PCA降维结果可视化（累计解释方差：{sum(explained_variance):.2%}）',
                    labels={
                        '主成分1': f'主成分1（解释方差：{explained_variance[0]:.2%}）',
                        '主成分2': f'主成分2（解释方差：{explained_variance[1]:.2%}）'
                    }
                )
                title = f'PCA降维结果可视化（累计解释方差：{sum(explained_variance):.2%}）'

            # 更新图表和标题
            plotly_container.update_figure(new_fig)
            chart_title.set_text(title)
            ui.notify(f"已更新为{chart_types[chart_type]}", type='positive')

    print("Web界面构建完成")


# 主程序入口（放在所有函数之外）
if __name__ in {"__main__", "__mp_main__"}:
    print("===== 学生社交媒体使用分析系统启动 =====")
    # 创建必要的文件夹
    for folder in ['data', 'visualizations', 'report']:
        os.makedirs(folder, exist_ok=True)
        print(f"确保文件夹存在: {folder}")

    # 定义数据文件路径（请根据实际路径修改）
    file_path = r"D:\Users\Redolbook\PycharmProjects\PythonProject课设\data\processed_dataset.csv"
    print(f"数据文件路径: {file_path}")

    # 加载并预处理数据
    df = load_csv_data(file_path)
    if df is None:
        print("数据加载失败，程序退出")
        exit(1)

    if not check_data(df):
        print("数据检查未通过，程序无法继续运行")
        exit(1)

    df_cleaned = preprocess_data(df)
    if df_cleaned.empty:
        print("预处理后数据为空，程序无法继续运行")
        exit(1)

    # 执行数据降维（PCA）
    reduced_df, explained_variance = reduce_data_dimension(df_cleaned)

    # 数据分析与可视化
    analysis_results = data_analysis(df_cleaned, reduced_df)
    visualizations = create_visualizations(df_cleaned, analysis_results, explained_variance)

    # 保存结果文件
    df_cleaned.to_csv('data/processed_dataset.csv', index=False)
    print("已保存预处理后的数据至 data/processed_dataset.csv")

    for name, fig in visualizations.items():
        fig.write_html(f'visualizations/{name}.html')
        print(f"已保存图表: visualizations/{name}.html")

    # 启动Web界面
    initial_fig = visualizations.get('scatter', px.scatter(title='无数据可视化'))
    build_interface(initial_fig, df_cleaned, reduced_df)

    # 启动服务器
    print("\n启动Web服务器...")
    print("访问地址: http://127.0.0.1:8080")
    ui.run(title='学生社交媒体使用分析系统', port=8080, show=True, reload=False)